package com.sunday.study.aspectJ;

import com.sunday.study.aspectJ.generic.MyType;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.math.BigDecimal;

/**
 * @Description: aop 学习与认知 ， 天下的知识从没有不劳而获的
 * <p>
 * Spring AOP 切面类型
 * Advisor：代表一般切面，Advice 本身就是一个切面，对目标类所有方法进行拦截
 * PointcutAdvisor：代表具有切点的切面，可以指定拦截目标类哪些方法(实现时需要配置切面信息，即类中要增强的方法设置bean)
 * IntroductionAdvisor：代表引介切面，针对引介通知而使用切面（不要求掌握）
 * <p>
 * 支持的切入点指示符
 * Spring AOP 支持以下 AspectJ 切点指示符 （PCD） 用于切中 表达 式：
 * <p>
 * execution：用于匹配方法执行连接点。这是主要的 使用弹簧 AOP 时要使用的切入点指示符。
 * <p>
 * within：限制匹配以某些类型中的连接点（执行 使用 Spring AOP 时在匹配类型中声明的方法）。
 * <p>
 * this：限制与连接点的匹配（使用 Spring 时执行方法 AOP），其中 Bean 引用（Spring AOP 代理）是给定类型的实例。
 * <p>
 * target：限制与连接点的匹配（使用时执行方法 Spring AOP），其中目标对象（被代理的应用程序对象）是一个实例 给定类型。
 * <p>
 * args：限制与连接点的匹配（使用 Spring 时执行方法 AOP），其中参数是给定类型的实例。
 * @target：限制与连接点的匹配（使用时执行方法 Spring AOP），其中执行对象的类具有给定类型的注释。
 * @args：限制与连接点的匹配（使用 Spring 时执行方法 AOP），其中传递的实际参数的运行时类型具有 给定类型。
 * @within：限制匹配以在具有给定的类型中的连接点 注释（在以下情况下执行在具有给定注释的类型中声明的方法 使用弹簧AOP）。
 * @annotation：限制与连接点的主题所在的连接点的匹配 （在 Spring AOP 中运行的方法）具有给定的注释。
 * <p>
 * bean(idOrNameOfBean)
 * <p>
 * 令牌可以是任何春豆的名称。有限的通配符 提供了使用该字符的支持，因此，如果您建立一些命名 Spring bean 的约定，
 * 您可以编写 PCD 表达式 以选择它们。与其他切点指示器一样，PCD 可以 也可以与 （and）、（or） 和 （否定） 运算符一起使用。
 * idOrNameOfBean * bean bean && || !
 * @Author: zsy
 * @Create: 2022/12/8 15:47
 */
@Slf4j
@Component
@Aspect
public class CommonPointcuts {

    /**
     * https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
     * execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
     *
     * modifiers-pattern：方法的可见性，如public，protected；
     * ret-type-pattern：方法的返回值类型，如int，void等；
     * declaring-type-pattern：方法所在类的全路径名，如com.spring.Aspect；
     * name-pattern：方法名类型，如buisinessService()；
     * param-pattern：方法的参数类型，如java.lang.String；
     * throws-pattern：方法抛出的异常类型，如java.lang.Exception；
     *
     */

    /**
     * 任何公共方法的执行：
     * execution(public * *(..))
     * <p>
     * 执行名称以 开头的任何方法：pay
     * execution(* pay*(..))
     * <p>
     * 接口定义的任何方法的执行：OrderHandler
     * execution(* com.sunday.study.aspectJ.execution.OrderHandler.*(..))
     * <p>
     * 执行包中定义的任何方法：service
     * execution(* com.sunday.study.aspectJ.execution.*.*(..))
     */
//    @Around("execution(* pay*(..))")
//    @Around("execution(* com.sunday.study.aspectJ.execution.OrderHandler.*(..))")
//    @Around("execution(* com.sunday.study.aspectJ.execution.*.*(..))")
//    @Around("execution(* com.sunday.study.aspectJ.execution.OrderHandler.*(..)) && @annotation(com.sunday.study.aspectJ.MyAnno)")
//    @Around("execution(@com.sunday.study.aspectJ.MyAnno * com.sunday.study.aspectJ..*.*(..))")
//    @Around("execution(* refund(..))")
//    @Around("execution(* com.sunday.study.aspectJ.execution.OrderHandler.pay(..)) || execution(* com.sunday.study.aspectJ.execution.OrderHandler.refund(..))")
    public Object execution(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[execution]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[execution]around after advice ...");
        return ret;
    }

    /**
     * 服务包中的任何连接点（仅在Spring AOP中执行方法）：
     * within(com.sunday.study.aspectJ.within.*)
     * <p>
     * 服务包或其之一中的任何连接点（仅在 Spring AOP 中执行方法） 子包：
     * within(com.sunday.study.aspectJ..*)
     */
//    @Around("within(com.sunday.study.aspectJ.within.*)")
//    @Around("within(com.sunday.study.aspectJ.*)")
//    @Around("within(com.sunday.study.aspectJ..*)")
    public Object within(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[within]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[within]around after advice ...");
        return ret;
    }

    /**
     * 代理实现接口的任何连接点（仅在 Spring AOP 中执行方法）：OrderHandler
     * this(com.sunday.study.aspectJ.execution.OrderHandler)
     */
//    @Around("this(com.sunday.study.aspectJ.execution.OrderHandler)")
    public Object thisPattern(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[this]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[this]around after advice ...");
        return ret;
    }

    /**
     * 目标对象的任何连接点（仅在 Spring AOP 中执行方法） 实现接口：AccountService
     * target(com.sunday.study.aspectJ.execution.OrderHandler)
     */
//    @Around("target(com.sunday.study.aspectJ.execution.OrderHandler)")
    public Object targetPattern(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[target]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[target]around after advice ...");
        return ret;
    }

    /**
     * Spring Bean 上名为 ：tradeService
     * bean(tradeService)
     * <p>
     * Spring bean 上的任何连接点（仅在 Spring AOP 中执行方法），其名称为 匹配通配符表达式：*Service
     * bean(*Service)
     */
//    @Around("bean(applyHandler)")
//    @Around("bean(*Handler)")
    public Object beanPattern(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[bean]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[bean]around after advice ...");
        return ret;
    }

    /**
     * 请注意，此示例中给出的切入点与 不同。如果运行时传递的参数为 ，则 args 版本匹配，如果方法签名声明单个，则执行版本匹配 类型的参数。execution(* *(java.io.Serializable))SerializableSerializable
     * <p>
     * 目标对象具有注释的任何连接点（仅在Spring AOP中执行方法）：@Transactional
     *
     * @target(org.springframework.transaction.annotation.Transactional) 也可以以绑定形式使用。请参阅声明建议部分 如何使批注对象在建议正文中可用。@target
     * <p>
     * 任何连接点（仅在 Spring AOP 中执行方法），其中声明的类型为 目标对象有一个注释：@Transactional
     * @within(org.springframework.transaction.annotation.Transactional) 也可以以绑定形式使用。请参阅声明建议部分 如何使批注对象在建议正文中可用。@within
     * <p>
     * 执行方法具有注释的任何连接点（仅在Spring AOP中执行方法）：@Transactional
     * @annotation(org.springframework.transaction.annotation.Transactional) 也可以以绑定形式使用。请参阅声明建议部分 了解如何使注释对象在建议正文中可用。@annotation
     * <p>
     * 任何采用单个参数的连接点（仅在Spring AOP中执行方法）， 其中传递的参数的运行时类型具有注释：@Classified
     * @args(com.xyz.security.Classified)
     */
//    @Around("@annotation(org.springframework.context.annotation.Lazy)")
//    @Around("@within(com.sunday.study.aspectJ.MyAnno)")
//    @Around("@target(com.sunday.study.aspectJ.MyAnno)")
    public Object annotationPattern(ProceedingJoinPoint pjp) throws Throwable {
        log.info("[@annotation]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("[@annotation]around after advice ...");
        return ret;
    }

    /**
     * io.github.resilience4j.spring6.circuitbreaker.configure.CircuitBreakerAspect
     *
     * @within(myAnno) 对应的是包含MyAnno注解的类。当一个类被MyAnno这个注解标记时，这个类中的所有方法都会匹配这个切点，不论这些方法上是否直接有MyAnno注解。
     * @annotation(myAnno) 对应的是直接在方法上使用了MyAnno注解的情况。只有具体标记了MyAnno注解的方法才会匹配这个切点。这并不关心该方法所在的类是否被MyAnno注解标记。
     *
     * @MyAnno // 这个注解使得这个类中的所有方法都匹配 @within(myAnno) 的要求
     * public class SomeClass {
     *     // method1 匹配 @within(myAnno)（因为它在一个标有 MyAnno 注解的类中）
     *     // 但不匹配 @annotation(myAnno)（因为 method1 上面没有直接标注 MyAnno）
     *     public void method1() {
     *     }
     *
     *     @MyAnno // 这个注解使得 method2 既匹配 @within(myAnno) 又匹配 @annotation(myAnno)
     *     public void method2() {
     *     }
     * }
     *
     * public class OtherClass {
     *     // method3 不匹配 @within(myAnno)（因为它不在标有 MyAnno 的类中）
     *     // 但匹配 @annotation(myAnno)（因为 method3 上面直接标注了 MyAnno）
     *     @MyAnno
     *     public void method3() {
     *     }
     *
     *     // method4 既不匹配 @within(myAnno) 也不匹配 @annotation(myAnno)
     *     // 因为它既不在标有 MyAnno 注解的类中，method 上也没有直接标注 MyAnno
     *     public void method4() {
     *     }
     * }
     * 在SomeClass中，@within(myAnno)会匹配method1和method2，因为SomeClass类上有MyAnno注解。@annotation(myAnno)只会匹配method2，因为只有它直接在方法上使用了MyAnno注解。而在OtherClass中，@within(myAnno)不会匹配任何方法，因为这个类未被MyAnno注解标记，不过@annotation(myAnno)会匹配method3因为它直接被MyAnno注解标记。
     *
     */
    @Pointcut(value = "@within(myAnno) || @annotation(myAnno)", argNames = "myAnno")
    public void matchAnnotatedClassOrMethod(MyAnno myAnno) {
    }

    @Around(value = "matchAnnotatedClassOrMethod(myAnno)", argNames = "proceedingJoinPoint, myAnno")
    public Object circuitBreakerAroundAdvice(ProceedingJoinPoint proceedingJoinPoint,
                                             @Nullable MyAnno myAnno) throws Throwable {
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        String methodName = STR."\{method.getDeclaringClass().getName()}#\{method.getName()}";
        log.info("circuitBreakerAroundAdvice : {}", methodName);
        log.info(myAnno.toString());
        return proceedingJoinPoint.proceed();
    }

    /**
     * 访问当前JoinPoint
     * 任何建议方法都可以声明一个类型的参数作为其第一个参数。请注意，需要围绕建议声明第一个 类型的参数，它是 的子类。org.aspectj.lang.JoinPointProceedingJoinPointJoinPoint
     * 该接口提供了许多有用的方法：JoinPoint
     * getArgs()：返回方法参数。
     * getThis()：返回代理对象。
     * getTarget()：返回目标对象。
     * getSignature()：返回建议的方法的说明。
     * toString()：打印建议方法的有用说明。
     */
//    @Around("execution(* com.sunday.study.aspectJ.execution.OrderHandler.*(..)) && args(money,..)")
    public Object args(ProceedingJoinPoint pjp, BigDecimal money) throws Throwable {
        log.info("around before advice ...");
        log.info("getArgs()：返回方法参数 : {}", pjp.getArgs());
        log.info("getThis()：返回代理对象 : {}", pjp.getThis());
        log.info("getTarget()：返回目标对象 : {}", pjp.getTarget());
        log.info("getSignature()：返回建议的方法的说明 : {}", pjp.getSignature());
        log.info("toString()：打印建议方法的有用说明 : {}", pjp.toString());

        log.info("args(money,..) : {}", money);
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("around after advice ...");
        return ret;
    }

    /**
     * 建议参数和泛型
     * Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个泛型类型，如下所示：
     * <p>
     * public interface Sample<T> {
     * void sampleGenericMethod(T param);
     * void sampleGenericCollectionMethod(Collection<T> param);
     * }
     * <p>
     * 您可以通过以下方式将方法类型的截获限制为某些参数类型 将 advice 参数绑定到要截获该方法的参数类型：
     *
     * @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
     * public void beforeSampleMethod(MyType param) {
     * // Advice implementation
     * }
     * <p>
     * 此方法不适用于泛型集合。所以你不能定义一个 切入点如下：
     * <p>
     * 爪哇岛科特林
     * @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
     * public void beforeSampleMethod(Collection<MyType> param) {
     * // Advice implementation
     * }
     * <p>
     * 为了完成这项工作，我们必须检查集合的每个元素，这不是 合理，因为我们也无法决定如何对待一般价值观。要实现 与此类似的东西，您必须键入参数并手动 检查元素的类型。nullCollection<?>
     */
//    @Around("execution(* *.sampleGenericMethod(*)) && args(param)")
    public Object generic(ProceedingJoinPoint pjp, MyType param) throws Throwable {
//    @Around("execution(* *.sampleGenericCollectionMethod(*)) && args(param)")
//    public Object generic(ProceedingJoinPoint pjp, Collection<MyType> param) throws Throwable {
        log.info("[generic]around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        log.info("param : {}", param);
        log.info("[@generic]around after advice ...");
        return ret;
    }


}
